/*-
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package ca.uhn.fhir.rest.param;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;

import java.util.Objects;

import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

/**
 * Base class for RESTful operation parameter types
 */
public abstract class BaseParam implements IQueryParameterType {

	private Boolean myMissing;

	abstract String doGetQueryParameterQualifier();

	abstract String doGetValueAsQueryToken();

	abstract void doSetValueAsQueryToken(
			FhirContext theContext, String theParamName, String theQualifier, String theValue);

	/**
	 * If set to non-null value, indicates that this parameter has been populated with a "[name]:missing=true" or "[name]:missing=false" vale instead of a normal value
	 */
	@Override
	public Boolean getMissing() {
		return myMissing;
	}

	/**
	 * If set to non-null value, indicates that this parameter has been populated
	 * with a "[name]:missing=true" or "[name]:missing=false" value instead of a
	 * normal value
	 *
	 * @return Returns a reference to <code>this</code> for easier method chaining
	 */
	@Override
	public BaseParam setMissing(Boolean theMissing) {
		myMissing = theMissing;
		return this;
	}

	@Override
	public final String getQueryParameterQualifier() {
		if (myMissing != null) {
			return Constants.PARAMQUALIFIER_MISSING;
		}
		return doGetQueryParameterQualifier();
	}

	@Override
	public final String getValueAsQueryToken() {
		if (myMissing != null) {
			return myMissing ? Constants.PARAMQUALIFIER_MISSING_TRUE : Constants.PARAMQUALIFIER_MISSING_FALSE;
		}
		return doGetValueAsQueryToken();
	}

	/**
	 * Does this parameter type support chained parameters (only reference should return <code>true</code> for this)
	 */
	protected boolean isSupportsChain() {
		return false;
	}

	@Override
	public final void setValueAsQueryToken(
			FhirContext theContext, String theParamName, String theQualifier, String theValue) {
		if (Constants.PARAMQUALIFIER_MISSING.equals(theQualifier)) {
			myMissing = "true".equals(theValue);
			doSetValueAsQueryToken(theContext, theParamName, null, null);
		} else {
			if (isNotBlank(theQualifier) && theQualifier.charAt(0) == '.') {
				if (!isSupportsChain()) {
					String msg = theContext
							.getLocalizer()
							.getMessage(
									BaseParam.class, "chainNotSupported", theParamName + theQualifier, theQualifier);
					throw new InvalidRequestException(Msg.code(1935) + msg);
				}
			}

			myMissing = null;
			doSetValueAsQueryToken(theContext, theParamName, theQualifier, theValue);
		}
	}

	/**
	 * Returns {@literal true} if this parameter has a {@literal null} or empty {@link #getValueAsQueryToken()}
	 * value, and doesn't have a {@link #getMissing()} value.
	 *
	 * @since 8.4.0
	 */
	public boolean isEmpty() {
		return isBlank(getValueAsQueryToken()) && getMissing() == null;
	}

	@Override
	public boolean equals(Object theO) {
		if (!(theO instanceof BaseParam baseParam)) {
			return false;
		}
		return Objects.equals(this.getValueAsQueryToken(), baseParam.getValueAsQueryToken())
				&& Objects.equals(myMissing, baseParam.myMissing);
	}

	@Override
	public int hashCode() {
		return Objects.hash(getValueAsQueryToken(), myMissing);
	}
}
